/*
 * $Author: cbeams $
 * $Date: 2007-06-04 19:47:27 +0000 (Mon, 04 Jun 2007) $
 * $Rev: 26 $
 */
package com.db4o.eclipse.plugin.views;

import com.db4o.Db4o;
import com.db4o.ObjectContainer;

import com.db4o.eclipse.plugin.model.Db4oDatabase;
import com.db4o.eclipse.plugin.model.Db4oStoredClass;
import com.db4o.eclipse.plugin.model.Db4oStoredObjectInstance;
import com.db4o.eclipse.plugin.model.Db4oStoredObjectInstanceField;
import com.db4o.eclipse.plugin.model.InvisibleRoot;
import com.db4o.eclipse.plugin.model.TreeObject;
import com.db4o.eclipse.plugin.model.TreeParent;

import com.db4o.ext.DatabaseFileLockedException;
import com.db4o.ext.Db4oException;

import com.db4o.reflect.ReflectClass;
import com.db4o.reflect.ReflectField;

import org.eclipse.jface.action.*;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.*;

import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Menu;

import org.eclipse.ui.*;
import org.eclipse.ui.part.*;

import java.io.File;


/**
 * This sample class demonstrates how to plug-in a new workbench view. The view shows data obtained
 * from the model. The sample creates a dummy model on the fly, but a real implementation would
 * connect to the model available either in this or another plug-in (e.g. the workspace). The view
 * is connected to the model using a content provider.
 *
 * <p>The view uses a label provider to define how model objects should be presented in the view.
 * Each view can present the same model objects using different labels and icons, if needed.
 * Alternatively, a single label provider can be shared between views in order to ensure that
 * objects of the same type are presented in the same way everywhere.</p>
 */

public class Db4oExplorerView extends ViewPart {
    public static final String ID = Db4oExplorerView.class.getName();

    private TreeViewer viewer;
    private DrillDownAdapter drillDownAdapter;
    private Action openDatabaseFileAction;
    private Action action2;
    private Action doubleClickAction;

    private InvisibleRoot invisibleRoot = new InvisibleRoot();

    private ISelectionListener pageSelectionListener;

    /** The constructor. */
    public Db4oExplorerView() {}

    /**
     * This is a callback that will allow us to create the viewer and initialize it.
     *
     * @param  parent  TODO: DOCUMENT ME!
     */
    @Override
    public void createPartControl(Composite parent) {
        createTreeViewer(parent);
        makeActions();
        hookContextMenu();
        hookDoubleClickAction();
        contributeToActionBars();
        hookPageSelection();
    }

    private void hookPageSelection() {
        pageSelectionListener =
            new ISelectionListener() {
                public void selectionChanged(IWorkbenchPart part, ISelection selection) {
                    pageSelectionChanged(part, selection);
                }
            };

        getSite().getPage().addPostSelectionListener(pageSelectionListener);
    }

    private void pageSelectionChanged(IWorkbenchPart part, ISelection selection) {
        System.out.println("pageSelectionChanged invoked: " + selection);
        /*
        if (part == this)
          return;
        if (!(selection instanceof IStructuredSelection))
          return;
        IStructuredSelection sel = (IStructuredSelection) selection;
        IFavoriteItem[] items = FavoritesManager.getManager()
          .existingFavoritesFor(sel.iterator());
        if (items.length > 0)
          viewer.setSelection(new StructuredSelection(items), true);
          */
    }

    private void createTreeViewer(Composite parent) {
        viewer = new TreeViewer(parent, SWT.SINGLE | SWT.H_SCROLL | SWT.V_SCROLL);
        drillDownAdapter = new DrillDownAdapter(viewer);
        viewer.setContentProvider(new ViewContentProvider());
        viewer.setLabelProvider(new ViewLabelProvider());
        viewer.setSorter(new NameSorter());
        viewer.setInput(getInitialInput());
        viewer.addTreeListener(new ExpandAndCollapseListener());

        viewer.addSelectionChangedListener(new ISelectionChangedListener() {
                public void selectionChanged(SelectionChangedEvent event) {
                    System.out.println("selected: " + event.getSelection());
                }
            });

        getSite().setSelectionProvider(viewer);
    }

    private InvisibleRoot getInitialInput() {
        return invisibleRoot;
    }

    private void hookContextMenu() {
        MenuManager menuMgr = new MenuManager("#PopupMenu");
        menuMgr.setRemoveAllWhenShown(true);
        menuMgr.addMenuListener(new IMenuListener() {
                public void menuAboutToShow(IMenuManager manager) {
                    Db4oExplorerView.this.fillContextMenu(manager);
                }
            });

        Menu menu = menuMgr.createContextMenu(viewer.getControl());
        viewer.getControl().setMenu(menu);
        getSite().registerContextMenu(menuMgr, viewer);
    }

    private void contributeToActionBars() {
        IActionBars bars = getViewSite().getActionBars();
        fillLocalPullDown(bars.getMenuManager());
        fillLocalToolBar(bars.getToolBarManager());
    }

    private void fillLocalPullDown(IMenuManager manager) {
        manager.add(openDatabaseFileAction);
        manager.add(new Separator());
        manager.add(action2);
    }

    private void fillContextMenu(IMenuManager manager) {
        manager.add(openDatabaseFileAction);
        manager.add(action2);
        manager.add(new Separator());
        drillDownAdapter.addNavigationActions(manager);
        // Other plug-ins can contribute there actions here
        manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
    }

    private void fillLocalToolBar(IToolBarManager manager) {
        manager.add(openDatabaseFileAction);
        manager.add(action2);
        manager.add(new Separator());
        drillDownAdapter.addNavigationActions(manager);
    }

    private void makeActions() {
        // =====================================================================
        // Create an action that will allow the user to select a db4o database
        // file from a file chooser dialog
        // =====================================================================
        openDatabaseFileAction =
            new Action() {
                @Override
                public void run() {
                    // the FileDialog is modal, so the open() method will block.
                    FileDialog fd = new FileDialog(viewer.getControl().getShell(), SWT.OPEN);
                    fd.open();

                    // =========================================================
                    // If the user doesn't choose anything or clicks ESC/Cancel
                    // getFileName() will return empty string ("")
                    // =========================================================
                    String parentDir = fd.getFilterPath();
                    String fileName = fd.getFileName();
                    if ((fileName != null) && !fileName.equals("")) {
                        File file = new File(parentDir, fileName);
                        String filePath = file.getPath();
                        try {
                            ObjectContainer oc = Db4o.openFile(filePath);
                            invisibleRoot.addChild(new Db4oDatabase(filePath, oc));
                            viewer.refresh();
                        } catch (DatabaseFileLockedException e) {
                            MessageDialog.openError(viewer.getControl().getShell(),
                                                    "Database locked",
                                                    e.getMessage());
                        } catch (Db4oException e) {
                            // =================================================
                            // usually if there is an exception thrown it's
                            // because the user has chosen a non-db4o file.
                            // The message will be: "incompatible file format"
                            // =================================================
                            MessageDialog.openError(viewer.getControl().getShell(),
                                                    "Error opening file",
                                                    e.getMessage());
                        }
                    }
                }
            };
        openDatabaseFileAction.setText("Open");
        openDatabaseFileAction.setToolTipText("Open a new database file");
        openDatabaseFileAction.setImageDescriptor(Db4oIcons.OPEN_DB_FILE.getImageDescriptor());

        action2 =
            new Action() {
                @Override
                public void run() {
                    showMessage("Action 2 executed");
                }
            };
        action2.setText("Action 2");
        action2.setToolTipText("Action 2 tooltip");
        action2.setImageDescriptor(PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(ISharedImages.IMG_OBJS_INFO_TSK));
        doubleClickAction =
            new Action() {
                @Override
                public void run() {
                    ISelection selection = viewer.getSelection();
                    Object obj = ((IStructuredSelection) selection).getFirstElement();
                    showMessage("Double-click detected on " + obj);
                }
            };
    }

    private void hookDoubleClickAction() {
        viewer.addDoubleClickListener(new IDoubleClickListener() {
                public void doubleClick(DoubleClickEvent event) {
                    doubleClickAction.run();
                }
            });
    }

    private void showMessage(String message) {
        MessageDialog.openInformation(viewer.getControl().getShell(),
                                      "Sample View",
                                      message);
    }

    /** Passing the focus request to the viewer's control. */
    @Override
    public void setFocus() {
        viewer.getControl().setFocus();
    }

    private class ExpandAndCollapseListener implements ITreeViewerListener {

        public void treeCollapsed(TreeExpansionEvent event) {
            ; // no-op
        }

        /**
         * Catches expansion events on the field level and dynamically populates them with their
         * children. This is necessary to avoid performance problems or infinite loops with very
         * deep graphs or bidirectional relationships
         *
         * @param  event  the event object
         */
        public void treeExpanded(final TreeExpansionEvent event) {

            if (event.getElement() instanceof Db4oStoredObjectInstanceField) {
                Db4oStoredObjectInstanceField elementBeingExpanded =
                    (Db4oStoredObjectInstanceField) event.getElement();

                if (elementBeingExpanded.isPopulated()) {
                    return;
                }

                ReflectField fieldBeingExpanded = elementBeingExpanded.getReflectField();

                ReflectField[] childFields = fieldBeingExpanded.getFieldType().getDeclaredFields();

                for (ReflectField childField : childFields) {
                    childField.setAccessible();
                    Object o = elementBeingExpanded.getObject();
                    Object childFieldValue = childField.get(o);
                    elementBeingExpanded.addChild(new Db4oStoredObjectInstanceField(childFieldValue,
                                                                                    childField));
                }

                // This is a hackish workaround for what looks to be a bug. Ideally,
                // `event.getTreeViewer().refresh(event.getElement())` would work, but it doesn't.
                // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=103747
                event.getTreeViewer().getControl().getDisplay().asyncExec(new Runnable() {
                        public void run() {
                            event.getTreeViewer().refresh(event.getElement());
                        }
                    });

                elementBeingExpanded.setPopulated(true);
            }
        }

    }


    /*
     * The content provider class is responsible for
     * providing objects to the view. It can wrap
     * existing objects in adapters or simply return
     * objects as-is. These objects may be sensitive
     * to the current input of the view, or ignore
     * it and always show the same content
     * (like Task List, for example).
     */

    class ViewContentProvider implements IStructuredContentProvider, ITreeContentProvider {

        public void inputChanged(Viewer v, Object oldInput, Object newInput) {}

        public void dispose() {}

        public Object[] getElements(Object parent) {
            if (parent.equals(getViewSite())) {
                if (invisibleRoot == null) {
                    // initialize();
                }

                return getChildren(invisibleRoot);
            }

            return getChildren(parent);
        }

        public Object getParent(Object child) {
            if (child instanceof TreeObject) {
                return ((TreeObject) child).getParent();
            }

            return null;
        }

        public Object[] getChildren(Object parent) {
            if (parent instanceof TreeParent) {
                return ((TreeParent) parent).getChildren();
            }

            return new Object[0];
        }

        public boolean hasChildren(Object parent) {
            if (parent instanceof TreeParent) {
                return ((TreeParent) parent).hasChildren();
            }

            return false;
        }
    }


    class ViewLabelProvider extends LabelProvider {

        @Override
        public String getText(Object obj) {
            return obj.toString();
        }

        @Override
        public Image getImage(Object obj) {
            Db4oIcons imageKey;

            if (obj instanceof Db4oDatabase) {
                imageKey = Db4oIcons.DATABASE;
            } else if (obj instanceof Db4oStoredClass) {
                ReflectClass cls = ((Db4oStoredClass) obj).getReflectClass();
                // TODO: add an enum type here
                if (cls.isInterface()) {
                    imageKey = Db4oIcons.INTERFACE;
                } else {
                    imageKey = Db4oIcons.CLASS;
                }
            } else if (obj instanceof Db4oStoredObjectInstance) {
                imageKey = Db4oIcons.INSTANCE;
            } else if (obj instanceof Db4oStoredObjectInstanceField) {
                // TODO: here is where we would differentiate between public/non-public labels
                // TODO: support static
                imageKey = Db4oIcons.FIELD;
            } else {
                imageKey = Db4oIcons.UNKNOWN;
            }

            return imageKey.getImage();
        }
    }


    class NameSorter extends ViewerSorter {}
}
